package cn.chenfeifx.web.controls;

import cn.chenfeifx.core.common.ClipboardUtils;
import cn.chenfeifx.core.common.ResourcesLoader;
import cn.chenfeifx.web.enums.AceMode;
import cn.chenfeifx.web.enums.AceTheme;
import javafx.beans.property.*;
import javafx.concurrent.Worker;
import javafx.scene.control.Button;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import netscape.javascript.JSObject;
import org.kordamp.ikonli.javafx.FontIcon;

/**
 * <a href="https://ace.c9.io/#howto">https://ace.c9.io/#howto</a>
 *
 * @author chenfei
 */
public class AceView extends StackPane {

    private final WebView webView = new WebView();
    private WebEngine engine;
    private AceMode mode = AceMode.JAVA;
    private JSObject window;
    private JSObject editor;

    public AceView() {
        initialize();
    }

    public AceView(String text) {
        setText(text);
        initialize();
    }

    public AceView(String text, AceMode mode) {
        setText(text);
        this.mode = mode;
        initialize();
    }

    public AceView(String text, AceMode mode, AceTheme theme) {
        setText(text);
        this.mode = mode;
        setTheme(theme);
        initialize();
    }

    public void initialize() {
        getChildren().addAll(webView);
        getStyleClass().add("ace-view");
        webView.setContextMenuEnabled(false);
        engine = webView.getEngine();
        // 界面加载完成执行的方法
        engine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
            if (newState == Worker.State.SUCCEEDED) {
                //
                window = (JSObject) engine.executeScript("window");
                window.setMember("AceView", this);
                editor = (JSObject) engine.executeScript("window.editor");
                setAceText(getText());
                setAceTheme(getTheme());
                setAceMode(mode);
                setAceReadOnly(isEditable());
                // WebView 整合 Ace.js 后 Ctrl+C 和 Ctrl+X 有问题，重新处理下。
                webView.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                    if (event.isControlDown() && event.getCode() == KeyCode.C) {
                        event.consume();
                        ClipboardUtils.setString(selectedText());
                    }
                    if (event.isControlDown() && event.getCode() == KeyCode.X) {
                        event.consume();
                        ClipboardUtils.setString(selectedText());
                        if (isEditable()) {
                            removeSelectText(); // 删除选中的内容
                        }
                    }
                });
                // 主题监听
                themeProperty().addListener((observable, oldValue, newValue) -> setAceTheme(newValue));
                // 是否可编辑
                editable.addListener((observable, oldValue, newValue) -> setAceReadOnly(newValue));
                showGutter.addListener((observable, oldValue, newValue) -> setAceShowGutter(newValue));
            }
        });
        engine.setJavaScriptEnabled(true);
        // message print
        engine.getLoadWorker().messageProperty()
                .addListener((observable, oldValue, newValue) -> System.out.printf("engine message : %s%n", newValue));
        // exception print
        engine.getLoadWorker().exceptionProperty()
                .addListener((observable, oldValue, newValue) -> newValue.printStackTrace());
        engine.loadContent(AceHelper.getHtml(this.mode)); // 加载html资源
        // engine.load(""); // 同一时间多个WebView访问同一个资源时engine.executeScript();会返回undefined。
    }

    private final StringProperty text = new SimpleStringProperty("");

    public StringProperty textProperty() {
        return text;
    }

    public String getText() {
        return text.get();
    }

    private void setText(String text) {
        this.text.set(text);
    }

    /**
     * 主题
     */
    private final ObjectProperty<AceTheme> theme = new SimpleObjectProperty<>(AceTheme.CHROME);

    public ObjectProperty<AceTheme> themeProperty() {
        return theme;
    }

    public AceTheme getTheme() {
        return theme.get();
    }

    public void setTheme(AceTheme theme) {
        this.theme.set(theme);
    }

    /**
     * 是否可编辑
     */
    private final BooleanProperty editable = new SimpleBooleanProperty(true); // 可编辑

    public void setEditable(boolean editable) {
        this.editable.set(editable);
    }

    public boolean isEditable() {
        return editable.get();
    }

    private final BooleanProperty showGutter = new SimpleBooleanProperty(true);

    public void setShowGutter(boolean showGutter) {
        this.showGutter.set(showGutter);
    }

    public boolean isShowGutter() {
        return showGutter.get();
    }

    // 获取FontIcon按钮
    private Button getFontIconButton(FontIcon fontIcon) {
        Button button = new Button();
        button.getStyleClass().add("icon-button");
        button.setGraphic(fontIcon);
        return button;
    }


    @Override
    public String getUserAgentStylesheet() {
        return ResourcesLoader.load("/cn/chenfeifx/web/css/ace-view.css");
    }


    // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ Ace 相关功能 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓

    /**
     * 是否显示左侧内容（行号，折叠符号区域）
     *
     * @param val
     */
    private void setAceShowGutter(boolean val) {
        setAceOption("showGutter", val);
    }

    /**
     * 编辑器是否只读
     *
     * @param val
     */
    private void setAceReadOnly(boolean val) {
        //editor.call("setReadOnly", !val);
        setAceOption("readOnly", !val);
    }

    /**
     * 设置内容
     *
     * @param value
     */
    private void setAceText(String value) {
        editor.call("setValue", value, 1);
        // 或者调用js中定义好的方法
        // JSObject editor = (JSObject) engine.executeScript("window");
        // editor.call("setEditorValue", value);
    }

    /**
     * 获取内容
     *
     * @return
     */
    private String getAceText() {
        return (String) editor.call("getValue");
    }

    /**
     * Ace调整大小
     */
    public void resize() {
        editor.call("resize");
    }

    /**
     * 获取选定文本
     *
     * @return
     */
    public String selectedText() {
        // 一下两个方法都可以获取到选中的文本
        // String selectedText = (String) engine.executeScript("window.getSelection().toString()"); // 有些问题
        return (String) editor.call("getSelectedText");
    }

    /**
     * 在光标处插入，模拟用户输入
     */
    public void insert(String text) {
        editor.call("insert", text);
    }

    /**
     * 设置ace.js配置项
     *
     * @param key
     * @param value
     */
    private void setAceOption(String key, Object value) {
        editor.call("setOption", key, value);
    }

    /**
     * 删除选中的内容
     */
    private void removeSelectText() {
        window.call("removeSelectRange"); // 或者：editor.call("remove", "right");
    }

    /**
     * 修改主题
     */
    private void setAceTheme(AceTheme aceTheme) {
        editor.call("setTheme", aceTheme.getValue());
    }

    /**
     * 设置模式
     */
    private void setAceMode(AceMode mode) {
        window.call("setEditorMode", mode.getValue());
    }

    /**
     * 监听值变化，这个由js触发调用。
     *
     * @param text
     */
    public void textChange(String text) {
        setText(text);
    }

    /*
     *  editor.undo(); : 撤销操作，可以配合Ctrl+Z，设置是否撤销。
     */

}
